/**
 * Systemprogrammierung
 * Multiplayer-Quiz
 *
 * Server
 *
 * \file server/login.c:
 * \brief Implementierung des Logins */

#include "common/sockets.h"
#include "login.h"
#include "sync.h"
#include "main.h"
#include "user.h"
#include "clientthread.h"

/**	\brief Methode zum Spieler hinzufügen
 * 	Methode schreibt Name aus Login-Nachricht in Array mit 0-Terminierung
 * 	Übergibt Name und Client ID an Userdaten-Verwaltung
 * 	Bei erfolgreichem hinzufügen des Benutzers RÜckgabe der Client-ID ansonsten Fehler-ID
 *	\param client_sock Socket des Client
 * 	\param login_msg Struktur für Login-Request*/

uint8_t add_player(int client_sock, struct RFC_MSG login_msg) {

	if(login_msg.header.length == 0){
		return 255;
	}

	// Array für Name, Vorinitialisiert mit 0
	char name[32] = { 0 };

	// Hole Name von Datenpointer aus login_msg
	memcpy(&name, login_msg.data, login_msg.header.length);

	if (strchr(name, '<') != NULL || strchr(name, '>') != NULL || strchr(name, '|') != NULL || name[0] == ' ' || name[0] == '-') {
		return 255;
	}

	uint8_t client_id;

	// setze Userdaten Mutex
	userdata_mutex_lock();

	// Rufe add_user auf (Rückgabe = Client-ID bzw. Fehler-ID)
	client_id = add_user(client_sock, name);

	// gib Mutex frei
	userdata_mutex_unlock();

	return client_id;
}

/**	\brief Methode startet Client-Thread
 *	\param client_data zugewiesene Client-ID*/

uint8_t start_client_thread(uint8_t* client_data) {

	pthread_t thread;

	// Starte Client-Thread von User mit "client_id"
	if (pthread_create(&thread, NULL, client_thread, (void*) client_data) == -1) {
		perror("start_client_thread: ");
		return (THREAD_ERROR);
	}

	// Kopiere erzeugten Thread in Haupt-Client-Thread Array
	set_client_thread(*client_data, thread);

	return 0;
}

/**	\brief Login-Thread Startmethode
 *	\param param Standardübergabeparameter des Thread*/

void* login_thread(void* param) {

	// Hole Port aus main
	unsigned short port = get_port();

	/* ** ** ** Lokal benötigte Variablen ** ** */

	// listener socket, client-socket bei erfolgreichem accept
	int sock, client_sock;

	/* ** ** ** Starte Listener-Socket ** ** */

	// Erzeuge Listener Port auf Nummer "port"
	sock = create_server_socket(port);

	// Fange Fehler ab
	if (sock == -1) {
		debugPrint("Fehler beim erstellen des Sockets auf Port %d\n", port);
		//Beende Thread
		sock = SOCKET_ERROR;
		pthread_exit(&sock);
	}

	/* ** ** ** Daten für Client-RFC-Message ** ** */

	struct RFC_MSG client_msg;

	// Header-Werte auf 0 setzen
	client_msg.header.type = 0;
	client_msg.header.length = 0;

	/* ** ** ** Spielstatus (Vorbereitung oder Spiel) ** ** */

	int status;

	// Initialisiere Userdaten-Mutex
	if (create_userdata_mutex() == -1) {
		pthread_exit(&errno);
	}

	/* ** ** ** Schleife zum Warten auf neue Client-Connections ** ** */

	infoPrint("Warte auf Client-Connection...\n");

	while (1) {

		// Warte auf Clients, die zu sock-Socket connecten
		// und öffne neuen Socket, der in client_sock gespeichert wird.
		client_sock = accept(sock, 0, 0);

		// Verbindung nicht akzeptiert
		if (client_sock == -1) {
			debugPrint("Client Socket Connection Error\n");
			close(client_sock);
		} else {

			// Verbindung erfolgreich
			int send_success;
			uint8_t client_id = 0;

			debugPrint("Client verbunden - warte auf Login Request\n");

			/* ** ** ** Empfange RFC-Login-Message ** ** */

			switch (catch_com_err(receive_MSG(client_sock, &client_msg))) {

			// Wenn Login Request
			case LOGIN_REQUEST:

				// Hole aktuellen Spielstatus

				status_mutex_lock();

				status = get_game_status();

				status_mutex_unlock();

				/* ** ** ** Spiel in Spielphase ** ** */

				if (status == GAME_RUNNING) {

					// Error-Msg senden und Fehler beim Senden abfangen
					catch_com_err(send_err_MSG(client_sock, FATAL, "Auf dem Server findet gerade ein Spiel statt"));

					close(client_sock);

					break;

				} else {

					/* ** ** ** Spiel in Vorbereitungsphase ** ** */

					// Füge Spieler hinzu und erhalte Client-ID
					client_id = add_player(client_sock, client_msg);

					// Wenn Client-ID gültig
					if (client_id < MAX_USERS) {

						// Starte Client-Thread mit Client-ID
						start_client_thread(&client_id);
					}

					/* ** ** ** Fehler bei Spielererstellung behandeln ** ** */

					//Fataler Error
					switch (client_id) {

					case SERVER_FULL:

						send_success = send_err_MSG(client_sock, FATAL, "Server ist voll!");

						close(client_sock);

						break;

					case NAME_EXISTS:

						send_success = send_err_MSG(client_sock, FATAL, "Name bereits vergeben!");

						close(client_sock);

						break;

					case THREAD_ERROR:

						send_success = send_err_MSG(client_sock, FATAL, "Interner Server Error!");

						close(client_sock);

						break;

					case UNKNOWN_ERROR:

						send_success = send_err_MSG(client_sock, FATAL, "Unbekannter Server Fehler!");

						close(client_sock);

						break;

					case 255:

						send_success = send_err_MSG(client_sock, FATAL, "Ungültiger Name verwendet!");

						close(client_sock);

						break;

						/* ** ** ** Spieler wurde erfolgreich hinzugefügt ** ** */

					default:

						infoPrint("Client Login OK - Client-ID: %d\n", client_id);

						// Schicke Client-ID an Client
						send_success = send_MSG(client_sock, LOGIN_RESPONSE_OK, sizeof(client_id), &client_id);

						sem_post(&score_agent_sem);

						break;
					}

					/* ** ** ** Fehler beim Senden abfangen ** ** */

					if (catch_com_err(send_success) == COM_ERR) {
						//TODO Methode um bestimmten Client-Thread zu beenden und Client aus Userverwaltung zu streichen
					}

					break;
				}

			case COM_ERR:

				debugPrint("Client nicht mehr erreichbar\n");

				close(client_sock);

				break;

				// Keine Login-Request Message -> Tue nichts
			default:
				break;
			}

			// Prüfen ob beim Empfangen einer Nachricht Speicher allokiert wurde
			if ((client_msg.header.type != 0) && (client_msg.header.length != 0)) {

				// Freigeben des dynamischen Speichers (sockets. c - receive_MSG())
				free(client_msg.data);

				client_msg.data = NULL;
			}
		}
	}

	/* ** ** ** Login-Thread Ende ** ** */

	pthread_exit(NULL);
	return NULL;
}
